package com.yyon.mosaikan;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import org.anddev.andengine.engine.Engine;
import org.anddev.andengine.engine.camera.Camera;
import org.anddev.andengine.engine.handler.timer.ITimerCallback;
import org.anddev.andengine.engine.handler.timer.TimerHandler;
import org.anddev.andengine.engine.options.EngineOptions;
import org.anddev.andengine.engine.options.EngineOptions.ScreenOrientation;
import org.anddev.andengine.engine.options.resolutionpolicy.RatioResolutionPolicy;
import org.anddev.andengine.entity.Entity;
import org.anddev.andengine.entity.IEntity;
import org.anddev.andengine.entity.modifier.AlphaModifier;
import org.anddev.andengine.entity.modifier.ColorModifier;
import org.anddev.andengine.entity.modifier.RotationAtModifier;
import org.anddev.andengine.entity.modifier.RotationModifier;
import org.anddev.andengine.entity.modifier.ScaleAtModifier;
import org.anddev.andengine.entity.modifier.ScaleModifier;
import org.anddev.andengine.entity.primitive.Rectangle;
import org.anddev.andengine.entity.scene.Scene;
import org.anddev.andengine.entity.scene.Scene.IOnSceneTouchListener;
import org.anddev.andengine.entity.sprite.Sprite;
import org.anddev.andengine.entity.text.ChangeableText;
import org.anddev.andengine.entity.util.FPSLogger;
import org.anddev.andengine.input.touch.TouchEvent;
import org.anddev.andengine.opengl.font.Font;
import org.anddev.andengine.opengl.texture.Texture;
import org.anddev.andengine.opengl.texture.TextureOptions;
import org.anddev.andengine.opengl.texture.region.TextureRegion;
import org.anddev.andengine.opengl.texture.region.TextureRegionFactory;
import org.anddev.andengine.ui.activity.BaseGameActivity;
import org.anddev.andengine.util.modifier.IModifier;
import org.anddev.andengine.util.modifier.IModifier.IModifierListener;

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.Typeface;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;

/**
 * @author Ian Campbell
 */

/**
 * NOTE: official Todo list on code.google.com page
 */

public class Mosaikan extends BaseGameActivity implements IOnSceneTouchListener, ITimerCallback, IModifierListener<IEntity> {

    final int CAMERA_WIDTH = 480;
    final int CAMERA_HEIGHT = 320;
	
    private Camera camera;
    
    private Texture texture;
    private Texture backgroundtexture;
    private Texture tiletexture;
    
    private TextureRegion heroregion;
    private TextureRegion enemyregion;
    private TextureRegion background;
    private TextureRegion tileregion;

    float x = 0.68f;
    
    Sprite hero;
    private ArrayList<Sprite> tiles = new ArrayList<Sprite>();
    private ArrayList<EnemySprite> enemies = new ArrayList<EnemySprite>();
    private ArrayList<Sprite> shots = new ArrayList<Sprite>();

    private ArrayList<Sprite> heart_full = new ArrayList<Sprite>();
    private ArrayList<Sprite> heart_empty = new ArrayList<Sprite>();
        
    private ScaleAtModifier scale;
    
    private ArrayList<ArrayList<Integer>> map = new ArrayList<ArrayList<Integer>>();
	private ArrayList<Integer> Linearmap = new ArrayList<Integer>();
	
	private int maxHP=7;
	private int HP=7;
    
    private int lasttile;
    private float lasttouch;
	private float updatetime = 3f;
	private Texture shottexture;
	private TextureRegion shotregion;
	
	private TextureRegion heart_full_region;
	private TextureRegion heart_empty_region;
	private Texture heart_empty_texture;
	private Texture heart_full_texture;
	private Texture FontTexture;
	private Font Font;
	int score;
	private ChangeableText scoretext;
	private Rectangle scorebackground;
	private int license_dialog_id = 0;
	private Sprite backgroundsprite;
	private Texture grappltexture;
	private TextureRegion grapplregion;
	private Sprite grapplinghook;
	private int centerX;
	private int centerY;
	private boolean firstgrapplinghook = true;
	private Texture enemytexture;
	private RotationModifier herorotr;
	private RotationModifier herorotl;
	private Texture skytexture;
	private TextureRegion sky;
	private Texture nightskytexture;
	private TextureRegion nightsky;
	private Texture starstexture;
	private TextureRegion stars;
	private Texture galaxytexture;
	private TextureRegion galaxy;
	private Scene scene;
	private Sprite skysprite;
	private Sprite nightskysprite;
	private Sprite treessprite;
	private Sprite galaxysprite;
	private Sprite starssprite;
	private boolean lowqualitymode=false;
	private int gameoverid = 1;
	private boolean noenemiesmode;
	private String imgquality;
	private Entity enemylayer;
	private Entity shotslayer;
	private Entity tilelayer;
	private EnemySprite enemySpriteToRemove;
	private IEntity shotToRemove;
	
	@Override
    public Engine onLoadEngine() {
        SharedPreferences settings = getSharedPreferences("MosaikanPreferences", 0); // import settings
        this.lowqualitymode = settings.getBoolean("lowquality", false);
        this.noenemiesmode = settings.getBoolean("noenemies", false);
        this.imgquality = settings.getString("imgquality", "good");

	    this.camera = new Camera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT); // start andengine engine
    	return new Engine(new EngineOptions(true, ScreenOrientation.LANDSCAPE, new RatioResolutionPolicy(CAMERA_WIDTH, CAMERA_HEIGHT), this.camera).setNeedsSound(true));
    }

    @Override
    public void onLoadResources() {
    	TextureRegionFactory.setAssetBasePath("gfx/"); // set pictures directory to /assets/gfx
    	
    	TextureOptions texture = TextureOptions.BILINEAR_PREMULTIPLYALPHA; // change texture variables to imgquality setting
    	TextureOptions texture_lesser = TextureOptions.NEAREST_PREMULTIPLYALPHA;
    	TextureOptions texture_background = TextureOptions.NEAREST;
    	if (this.imgquality == "bad") {
    		texture = TextureOptions.NEAREST_PREMULTIPLYALPHA;
    		texture_lesser = TextureOptions.NEAREST_PREMULTIPLYALPHA;
    		texture_background = TextureOptions.NEAREST;
    	} else if (this.imgquality == "good") {
    		texture = TextureOptions.BILINEAR_PREMULTIPLYALPHA;
    		texture_lesser = TextureOptions.NEAREST_PREMULTIPLYALPHA;
    		texture_background = TextureOptions.NEAREST;
    	} else if (this.imgquality == "excellent") {
    		texture = TextureOptions.BILINEAR_PREMULTIPLYALPHA;
    		texture_lesser = TextureOptions.BILINEAR_PREMULTIPLYALPHA;
    		texture_background = TextureOptions.BILINEAR;
    	}
    	
    	this.texture = new Texture(64, 64, texture); //create all textures and regions
    	this.heroregion = TextureRegionFactory.createFromAsset(this.texture, this, "heropng3.png", 0, 0);

    	this.enemytexture = new Texture(64, 64, texture);
    	this.enemyregion = TextureRegionFactory.createFromAsset(this.enemytexture, this, "enemypng.png", 0, 0);

        this.backgroundtexture = new Texture(512,512, texture_background);
        TextureRegionFactory.createFromAsset(this.backgroundtexture, this, "trees.png", 0, 0);
        this.background = TextureRegionFactory.createFromAsset(this.backgroundtexture, this, "trees.png", 0, 0);

        if (lowqualitymode==false) { // (extra backgrounds -- only if lowqualitymode==false
	        this.skytexture = new Texture(1024,1024, texture_background);
	        this.sky = TextureRegionFactory.createFromAsset(this.skytexture, this, "sky.jpg", 0, 0);
	
	        this.nightskytexture = new Texture(1024,512, texture_background);
	        this.nightsky = TextureRegionFactory.createFromAsset(this.nightskytexture, this, "nightsky.jpg", 0, 0);
	
	        this.starstexture = new Texture(1024,1024, texture_background);
	        this.stars = TextureRegionFactory.createFromAsset(this.starstexture, this, "stars.jpg", 0, 0);
	
	        this.galaxytexture = new Texture(1024,1024, texture_background);
	        this.galaxy = TextureRegionFactory.createFromAsset(this.galaxytexture, this, "galaxy.jpg", 0, 0);
        }

        this.grappltexture = new Texture(64,512, texture_lesser);
        this.grapplregion = TextureRegionFactory.createFromAsset(this.grappltexture, this, "grapplinghook2.png", 0, 0);
        
    	this.tiletexture = new Texture(128, 64, texture);
        this.tileregion = TextureRegionFactory.createFromAsset(this.tiletexture, this, "tile.png", 0, 0);

    	this.shottexture = new Texture(32, 32, texture_lesser);
        this.shotregion = TextureRegionFactory.createFromAsset(this.shottexture, this, "shot.png", 0, 0);

    	this.heart_full_texture = new Texture(32, 32, texture);
        this.heart_full_region = TextureRegionFactory.createFromAsset(this.heart_full_texture, this, "heart-full.png", 0, 0);
        
    	this.heart_empty_texture = new Texture(32, 32, texture);
        this.heart_empty_region = TextureRegionFactory.createFromAsset(this.heart_empty_texture, this, "heart-empty.png", 0, 0);
        
        this.FontTexture = new Texture(256, 256, texture);

        this.Font = new Font(this.FontTexture, Typeface.create(Typeface.DEFAULT, Typeface.BOLD), 20, true, Color.WHITE);

        this.mEngine.getFontManager().loadFont(this.Font);
        
        // load all textures - remember to add textures here!
        this.mEngine.getTextureManager().loadTextures(this.enemytexture, this.shottexture, this.backgroundtexture, this.texture, this.tiletexture, this.heart_full_texture, this.heart_empty_texture, this.FontTexture, this.grappltexture);
        if (lowqualitymode==false) { // only load extra backgrounds if there are extra backgrounds
        	this.mEngine.getTextureManager().loadTextures(this.skytexture, this.starstexture, this.galaxytexture, this.nightskytexture);
        }
    }

	@Override
    public Scene onLoadScene() {
		this.mEngine.registerUpdateHandler(new FPSLogger()); //no idea what this does
		
        final Scene scene = new Scene(); //create scene
        this.scene = scene;
        scene.setBackgroundEnabled(false);
        
        if (lowqualitymode==false) { // load extra background sprites
	        this.galaxysprite = new Sprite(this.CAMERA_WIDTH/2-this.galaxy.getWidth()/2,this.CAMERA_HEIGHT/2-this.galaxy.getHeight()/2,this.galaxy);
	        this.scene.attachChild(this.galaxysprite);
	        
	        this.starssprite = new Sprite(this.CAMERA_WIDTH/2-this.stars.getWidth()/2,this.CAMERA_HEIGHT/2-this.stars.getHeight()/2,this.stars);
	        this.scene.attachChild(this.starssprite);

	        this.nightskysprite = new Sprite(this.CAMERA_WIDTH/2-this.nightsky.getWidth()/2,this.CAMERA_HEIGHT/2-this.nightsky.getHeight()/2,this.nightsky);
	        this.scene.attachChild(this.nightskysprite);

	        this.skysprite = new Sprite(this.CAMERA_WIDTH/2-this.sky.getWidth()/2,this.CAMERA_HEIGHT/2-this.sky.getHeight()/2,this.sky);
	        this.scene.attachChild(this.skysprite);
	        
	        scalebackground(skysprite);
        }
        
        this.treessprite = new Sprite(0,0,this.background); // load normal background
        this.backgroundsprite = this.treessprite;
        scene.attachChild(backgroundsprite);
		this.backgroundsprite.setRotationCenter(CAMERA_WIDTH/2, CAMERA_HEIGHT/2);
		this.backgroundsprite.setScaleCenter(CAMERA_WIDTH/2, CAMERA_HEIGHT/2);

        centerX = (CAMERA_WIDTH - this.heroregion.getWidth()) / 2; // position for hero
        centerY = CAMERA_HEIGHT - this.heroregion.getHeight()+10;

        tilelayer = new Entity(); // create layers that are placeholders for other objects (so they can be added later but slipped behind other objects)
        scene.attachChild(tilelayer);
        
        enemylayer = new Entity();
        scene.attachChild(enemylayer);

        shotslayer = new Entity();
        scene.attachChild(shotslayer);

        grapplinghook = new Sprite(centerX, centerY-16+10-356+32, this.grapplregion); // grappling hook
        grapplinghook.setVisible(false);
        scene.attachChild(grapplinghook);
		grapplinghook.registerEntityModifier(new ScaleAtModifier(0.01f, 1f/356f, 1f/356f, 32f, 356f));
        
        this.hero = new Sprite(centerX, centerY-16+10, this.heroregion); // hero
        scene.attachChild(hero);

        lasttile = makeMap(0, false); // generate map using makeMap
        for (int i=0; i<9; i+=1) {
        	lasttile = makeMap(lasttile, false);
        }

        for (int i=0; i<10; i++) { // Add tiles
        	for (int rot=0; rot<10; rot++) {
        		tiles.add(new Sprite((CAMERA_WIDTH-this.tileregion.getWidth()) / 2,(float) (CAMERA_HEIGHT - this.tileregion.getHeight())+30,this.tileregion)); // create tile at (0,0)
	            scale = new ScaleAtModifier(1, 1, (float) Math.pow(x, i), CAMERA_WIDTH/2-tiles.get(tiles.size()-1).getX(), CAMERA_HEIGHT/2-tiles.get(tiles.size()-1).getY()); // scale tile to correct Z position
	            tiles.get(tiles.size()-1).registerEntityModifier(scale);
	            RotationAtModifier rotate = new RotationAtModifier(1, 0, 36*rot, (float) this.tileregion.getWidth()/2, CAMERA_HEIGHT/2-tiles.get(tiles.size()-1).getY()); // rotate tile
	            tiles.get(tiles.size()-1).registerEntityModifier(rotate);
		        tilelayer.attachChild(tiles.get(tiles.size()-1)); // actually add tile
        	}
        }
    	
        scene.setOnSceneTouchListener(this); // tell engine to accept touching screen
        scene.setTouchAreaBindingEnabled(true);
        scene.registerTouchArea(hero);
        
        updatetiles(); // make some tiles invisible to match the map
        
        scene.registerUpdateHandler(new TimerHandler(updatetime, true, this)); // set timer for enemy shots
        
        scoretext = new ChangeableText(8, CAMERA_HEIGHT-this.Font.getLineHeight()-8, this.Font, "Score 0", "Score XXXXXXXXXX".length()); // make text for score
        score = 0;
        scorebackground = new Rectangle(6, CAMERA_HEIGHT-this.Font.getLineHeight()-6, this.Font.getStringWidth("Score 0")+4, this.Font.getLineHeight()-4);
        scorebackground.setColor(0,0,0);
        scene.attachChild(scorebackground);
        scene.attachChild(scoretext);
        
        updatescore(); // update text to say 0.
        
        float heartscale = 0.7f;
        for (int i=0; i<maxHP; i++) { // add hearts for health
            heart_empty.add(new Sprite(this.heart_empty_region.getWidth()*heartscale*(i+.5f), this.heart_empty_region.getHeight()*heartscale*.5f,this.heart_empty_region));
            heart_empty.get(i).registerEntityModifier(new ScaleModifier((float) 0.001, 1, heartscale));
            heart_empty.get(i).setVisible(false);
            scene.attachChild(heart_empty.get(i));
            
            heart_full.add(new Sprite(this.heart_full_region.getWidth()*heartscale*(i+.5f), this.heart_full_region.getHeight()*heartscale*.5f,this.heart_full_region));
            heart_full.get(i).registerEntityModifier(new ScaleModifier((float) 0.001, 1, heartscale));
            scene.attachChild(heart_full.get(i));
        }
               
        return scene; // next, run onLoadComplete
    }
    
	@Override
    public void onLoadComplete() {
		if (lowqualitymode == false) { // make the hero sway back and forth
			herorotr = new RotationModifier(1, -5f, 5f);
			herorotl = new RotationModifier(1, 5f, -5f);
			hero.registerEntityModifier(herorotr);
			herorotr.setModifierListener(this);
		}
	}

	@Override
	public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) { // If the screen is touched
		if (pSceneTouchEvent.getMotionEvent().getAction() == MotionEvent.ACTION_UP) {
			if (lasttouch-pSceneTouchEvent.getY() < -20) { // go forward
				moveZ();
			} else if (lasttouch-pSceneTouchEvent.getY() > 20) { // use grappling hook
				move5();
			} else { // go left or right
				if (pSceneTouchEvent.getX() < CAMERA_WIDTH/2) {
					move(-1);
				} else {
					move(1);
				}
			}
			updatetiles(); // make some tiles invisible to match map
		} else if (pSceneTouchEvent.getMotionEvent().getAction() == MotionEvent.ACTION_DOWN) {
			lasttouch = pSceneTouchEvent.getY(); // get the finger press down (to check for sliding)
		}
		
		return true;
	}
	
	public int makeMap(int lastindex, boolean eraseLast) { // generate random map
		Random random = new Random();
		if (eraseLast == true) {
			if (map.isEmpty() == false) {
				map.remove(0); // if eraselast=true, remove the map that goes off of the screen
			}
		}
		
		map.add(new ArrayList<Integer>()); // make the next 'circle' of the map (near the center of the screen)
        for (int i=0; i<10; i+=1) {
        	if (i==lastindex) {
        		map.get(map.size()-1).add((int) 1);
        	}
        	else {
        		map.get(map.size()-1).add((int) 0);
        	}
	    }
        int steps = random.nextInt(3)+1; // Generate a path to make sure that you can always go forward unless you go into a dead end
        for (int i=0; i<steps; i+=1) {
        	int x = random.nextInt(3);
        	if (x==0) {
        		lastindex = (lastindex-1)%10;
        		if (lastindex == -1) {
        			lastindex = 9;
        		}
        	}
        	else if (x==1) {
        		lastindex = (lastindex+1)%10;
        	}
        	else if (x==2) {
        		lastindex = (lastindex+5)%10;
        	}
        	map.get(map.size()-1).set(lastindex, (int) 1);
        }
        int randnum = random.nextInt(3)+1;
        for (int i=0; i<randnum; i+=1) {
        	map.get(map.size()-1).set(random.nextInt(10), (int) 1); // add random tiles for dead ends (and for disguising the path)
        }
		if (this.noenemiesmode==false) {
			if (random.nextInt((int) (10-((float)this.score)/2000f))==0) {
				int posR = 0;
				while (true) {
					posR = random.nextInt(10); // choose position for enemy
					if (((ArrayList<Integer>) map.get(map.size()-1)).get(posR) == 1) {
						break;
					}
				}
		    		if (eraseLast==false) { // add enemies randomly
		    			enemies.add(new EnemySprite((CAMERA_WIDTH-this.enemyregion.getWidth())/2,CAMERA_HEIGHT-this.tileregion.getHeight()+30, this.enemyregion, map.size()-1, posR, this));
		    		} else if (eraseLast==true) {
		    			enemies.add(new EnemySprite((CAMERA_WIDTH-this.enemyregion.getWidth())/2,CAMERA_HEIGHT-this.tileregion.getHeight()+30, this.enemyregion, map.size(), posR, this));
		    		}
		    		enemylayer.attachChild(enemies.get(enemies.size()-1));
			}
		}
        return lastindex;
	}
	
	public void moveZ() { // moving foward
		if (((ArrayList<Integer>) map.get(1)).get(0)==1) { // if there is a tile in front of the hero ...
			score += 10; // add 10 points
			updatescore(); // update the text to show that you got 10 points

			lasttile = makeMap(lasttile, true); // lengthen the map and get rid of the part of the map that goes off the screen
			updatetiles(); // make tiles show the new map
			
	        if (lowqualitymode==false) { // new backgrounds
				Float scalemultiplyer=1.01f; // how much the background will be zoomed in
				if (this.backgroundsprite == this.galaxysprite) {
					scalemultiplyer = 1.001f;
				}
				// zoom in background
				this.backgroundsprite.registerEntityModifier(new ScaleModifier(0.2f, backgroundsprite.getScaleX(), ((float)backgroundsprite.getScaleX())*scalemultiplyer));
				if (this.backgroundsprite != this.galaxysprite) { // make backgrounds fade into the next background
					this.backgroundsprite.registerEntityModifier(new AlphaModifier(0.2f, this.backgroundsprite.getAlpha(), 3-this.backgroundsprite.getScaleX()));
				}
				if (this.backgroundsprite.getScaleX() > 3) { // change backgrounds
					if (this.backgroundsprite == this.treessprite) {
			            this.runOnUpdateThread(new Runnable() {
			                @Override
			                public void run() {
			                		scene.detachChild(Mosaikan.this.treessprite);
			                }
			            });
						this.treessprite.setVisible(false);
					    
						this.backgroundsprite = this.skysprite;
						
						scalebackground(this.nightskysprite);
					} else if (this.backgroundsprite == this.skysprite) {
			            this.runOnUpdateThread(new Runnable() {
			                @Override
			                public void run() {
			                		scene.detachChild(Mosaikan.this.skysprite);
			                }
			            });
						this.skysprite.setVisible(false);
					    
						this.backgroundsprite = this.nightskysprite;
						
						scalebackground(this.starssprite);
					} else if (this.backgroundsprite == this.nightskysprite) {
			            this.runOnUpdateThread(new Runnable() {
			                @Override
			                public void run() {
			                		scene.detachChild(Mosaikan.this.nightskysprite);
			                }
			            });
						this.nightskysprite.setVisible(false);
					    
						this.backgroundsprite = this.starssprite;
						
						scalebackground(this.galaxysprite);
					} else if (this.backgroundsprite == this.starssprite) {
			            this.runOnUpdateThread(new Runnable() {
			                @Override
			                public void run() {
			                		scene.detachChild(Mosaikan.this.starssprite);
			                }
			            });
						this.starssprite.setVisible(false);
					    
						this.backgroundsprite = this.galaxysprite;
					}
				}
	        }
			
			for (int i = 0; i<enemies.size(); i++) {
				enemies.get(i).setZ(enemies.get(i).getZ()-1); // move enemies forward
			}
			boolean isdamaged = false;
			for (int i = 0; i<shots.size(); i++) { // move shots forward
				float newscale = (float) Math.pow(this.x, Math.log(shots.get(i).getScaleX())/Math.log(this.x)-1); // tile shot is over
				shots.get(i).clearEntityModifiers();
				if (newscale < 1) {
					scale = new ScaleAtModifier(0.3f/newscale, shots.get(i).getScaleX(), 1, this.CAMERA_WIDTH/2-shots.get(i).getX(), this.CAMERA_HEIGHT/2-shots.get(i).getY());
					shots.get(i).registerEntityModifier(scale);
					scale.setModifierListener(this);
				} else { // if the shot goes off of the screen .. 
					float pitemrot = shots.get(i).getRotation()%360;

					shots.get(i).setVisible(false);
					this.shotToRemove = shots.get(i);
		            this.runOnUpdateThread(new Runnable() {
		                @Override
		                public void run() {
		                        /* Now it is save to remove the entity! */
		                		shotslayer.detachChild(Mosaikan.this.shotToRemove); // remove shot
		                }
		            });
					shots.remove(i);
					
					if (pitemrot == 0) { // and it is at rotation 0 ... 
						isdamaged = true; // you got shot.
					}
				}
			}
			this.checkforenemydeath(); // check if you trampled any enemies (extra points!)
			if (isdamaged) {
				this.damage(); // if you got shot, damage
			}
		}
	}
	
	public void scalebackground(Sprite bkgr) { // zoom in backgrounds
		bkgr.setRotationCenter(bkgr.getWidth()/2, bkgr.getHeight()/2);
		bkgr.setScaleCenter(bkgr.getWidth()/2, bkgr.getHeight()/2);
		bkgr.registerEntityModifier(new ScaleModifier(0.01f, bkgr.getScaleX(), Math.max((float) ((float)this.CAMERA_WIDTH)/((float)bkgr.getWidth()), (float) ((float)this.CAMERA_HEIGHT)/((float)bkgr.getHeight()))));
	}

	void updatescore() { // update score text to match actual score
		scoretext.setText("Score "+score);
		scorebackground.setWidth(this.Font.getStringWidth("Score "+score)+4);
	}

	public void move(int move) { // move left or right
		if (((ArrayList<Integer>) map.get(0)).get((10-move)%10)==1) { // if there is a tile there ...
			for (int i=0; i<map.size(); i++) {
				ArrayList<Integer> curmap = map.get(i); // change map
				if (move==1) {
					curmap.add(0, curmap.get(curmap.size()-1));
					curmap.remove(curmap.size()-1);
				} else if (move==-1) {
					curmap.add(curmap.size(), curmap.get(0));
					curmap.remove(0);
				}
			}
			
//			this.backgroundsprite.setRotation(this.backgroundsprite.getRotation()+36*move); // rotate background
			
			lasttile += move; // change path ending (see makeMap)
			if (lasttile == 10) {
				lasttile = 0;
			}
			if (lasttile == -1) {
				lasttile = 9;
			}
			for (int i = 0; i<enemies.size(); i++) { // move enemies
				enemies.get(i).setR(enemies.get(i).getR()+move);
			}
			this.checkforenemydeath(); // did you trample an enemy?
			for (int i = 0; i<shots.size(); i++) { // move shots
				shots.get(i).registerEntityModifier(new RotationAtModifier(0.0001f, 0, shots.get(i).getRotation()+36*move, this.CAMERA_WIDTH/2-shots.get(i).getX(), this.CAMERA_HEIGHT/2-shots.get(i).getY()));
			}
		}
	}
	
	public void move5 () { // use grappling hook
		if (((ArrayList<Integer>) map.get(0)).get(5)==1) { // if there is a tile there
			grapplinghook.setVisible(true); // make grappling hook visible
			ScaleAtModifier grapplinghookscale;
			if (firstgrapplinghook==true) { // if this is the first time, make grappling hook slower so people know what the grappling hook does.
				grapplinghookscale = new ScaleAtModifier(1f, 1f/356f, 0.8f, 32f, 356f); // animate grappling hook
				firstgrapplinghook=false;
			} else {
				grapplinghookscale = new ScaleAtModifier(.15f, 1f/356f, 0.8f, 32f, 356f); // animate grappling hook
			}
			grapplinghook.registerEntityModifier(grapplinghookscale);
			grapplinghookscale.setModifierListener(this); // watch for when grappling hook is done animating
		}
	}
	
	public void updatetiles () { // make tiles invisible/visible to match map
		Linearmap.clear();
		for (int i=0; i<map.size(); i++) {
			for (int z=0; z<map.get(i).size(); z++) {
				Linearmap.add((Integer) map.get(i).get(z));
			}
		}
		for (int i=0; i<tiles.size(); i++) {
			if (Linearmap.get(i)==0) {
				tiles.get(i).setVisible(false);
			} else {
				tiles.get(i).setVisible(true);
			}
		}
	}

	@Override
	public void onTimePassed(TimerHandler pTimerHandler) { // make enemies shoot
		for (int i=0; i<enemies.size(); i++) {
			if (enemies.get(i).getZ()!=0) {
				// add shot
				shots.add(new Sprite(enemies.get(i).getX()+enemyregion.getWidth()/2-shotregion.getWidth()/2, enemies.get(i).getY(), this.shotregion));
				shotslayer.attachChild(shots.get(shots.size()-1));
				// make shots move by zooming animation
				ScaleAtModifier scale = new ScaleAtModifier(0.3f/(float) Math.pow(this.x, enemies.get(i).getZ()), (float) Math.pow(this.x, enemies.get(i).getZ()), 1, this.CAMERA_WIDTH/2-shots.get(shots.size()-1).getX(), this.CAMERA_HEIGHT/2-shots.get(shots.size()-1).getY());
				shots.get(shots.size()-1).registerEntityModifier(scale);
				scale.setModifierListener(this);
				// rotate shots to their correct rotation
		        RotationAtModifier rotate = new RotationAtModifier(0.01f, 0, 36*enemies.get(i).getR(), (float) shotregion.getWidth()/2, this.CAMERA_HEIGHT/2-enemies.get(i).getY());
		        shots.get(shots.size()-1).registerEntityModifier(rotate);
			}
		}
	}
	
	public void checkforenemydeath () { // has an enemy died?
		for (int i=0; i<enemies.size(); i++) {
			enemies.get(i).checkdeath(); // consult with the enemy class.
		}
	}

	@Override
	public void onModifierFinished(IModifier<IEntity> pModifier, IEntity pItem) { // end of an animation
		if (pItem == grapplinghook) { // grappling hook
			this.moveupwards(); // move upwards
		} else if (pItem == hero) { // hero is swaying
			if (pModifier==herorotr) { // make hero sway other way
				herorotl.reset();
				hero.registerEntityModifier(herorotl);
				herorotl.setModifierListener(this);
			} else if (pModifier==herorotl) {
				herorotr.reset();
				hero.registerEntityModifier(herorotr);
				herorotr.setModifierListener(this);
			}
		} else { // a shot has reached the edge of the map
			boolean isdamaged=false;
			float pitemrot = pItem.getRotation()%360;
			pItem.setVisible(false);
			this.shotToRemove = pItem;
            this.runOnUpdateThread(new Runnable() {
                @Override
                public void run() {
                        /* Now it is save to remove the entity! */
                		shotslayer.detachChild(Mosaikan.this.shotToRemove); // remove shot
                }
            });
			if (pitemrot == 0) {
				isdamaged=true; // has it hit the hero????
			}
			shots.remove(pItem);
			if (isdamaged) { 
				this.damage(); // if it has hit, damage hero
			}
		}
	}

	private void moveupwards() { // move across radius of tube
		grapplinghook.setVisible(false); // make grappling hook invisible
		grapplinghook.registerEntityModifier(new ScaleAtModifier(0.01f, 1f/356f, 1f/356f, 32f, 356f));
		
		if (((ArrayList<Integer>) map.get(0)).get(5)==1) { // is there still a tile there?
			for (int i=0; i<map.size(); i++) {
				ArrayList<Integer> curmap = map.get(i); // update map
				for (int w = 0; w<5; w++) {
					curmap.add(0, curmap.get(curmap.size()-1));
					curmap.remove(curmap.size()-1);
				}
			}
			lasttile += 5;
			lasttile = lasttile % 10;
	
			for (int i = 0; i<enemies.size(); i++) { // move enemies
				enemies.get(i).setR(enemies.get(i).getR()+5);
			}
			this.checkforenemydeath(); // have you trampled an enemy?
			for (int i = 0; i<shots.size(); i++) { // move shots
				shots.get(i).registerEntityModifier(new RotationAtModifier(0.0001f, 0, shots.get(i).getRotation()+36*5, this.CAMERA_WIDTH/2-shots.get(i).getX(), this.CAMERA_HEIGHT/2-shots.get(i).getY()));
			}
			updatetiles(); // make tiles invisible to reflect map
		}
	}

	private void damage() { // damage the hero
		this.HP -= 1; // -1 HP
		this.hero.registerEntityModifier(new ColorModifier(1, 1, 1, 0, 1, 0, 1)); // flash hero red
		score -= 50; // -50 points
		updatescore(); // change score text to actual score
		if (this.HP == 0) {
			this.kill(); // if HP==0, kill hero
		} else {
			heart_empty.get(this.HP).setVisible(true); // -1 heart
			heart_full.get(this.HP).setVisible(false);
		}
	}
	
	private void kill() { // are you dead?
		addnewhighscore(score); // add your score to the highscores
		this.runOnUiThread(new Runnable() {
			   @Override
			   public void run() {
				  Mosaikan.this.showDialog(gameoverid); // show game over screen
			   }
		});
	}
	
	public void addnewhighscore (int newscore) { // add new high score if it's in the top ten
        SharedPreferences settings = getSharedPreferences("MosaikanPreferences", 0); // get high scores in a string
        String[] strarrayscores = settings.getString("highscores", "0,0,0,0,0,0,0,0,0,0").split(","); // convert to string array
        Integer[] highscores = {0,0,0,0,0,0,0,0,0,0,0};
        for (int i=0; i<10; i++) {
        	highscores[i]=Integer.parseInt(strarrayscores[i]); //convert to integer array
        }
        highscores[10]=newscore; // add your score
        Arrays.sort(highscores); // sort 11 scores in ascending order
        List<Integer> list = Arrays.asList(highscores); // invert 11 scores
        Collections.reverse(list);
        highscores = (Integer[]) list.toArray();
        String strscores = "";
        for (int i=0; i<10; i++) {
        	strscores+=highscores[i].toString()+","; // change first 10 scores to string
        }
        strscores=(String) strscores.subSequence(0, strscores.length()-1);
	    SharedPreferences.Editor editor = settings.edit();
	    editor.putString("highscores", strscores); // save new high scores

	    editor.commit();
	}

	public void killenemy(EnemySprite enemySprite) { // kill enemy
		enemySprite.setVisible(false); // enemy now invisible
		enemies.remove(enemySprite); // remove enemy from enemy list
		this.enemySpriteToRemove = enemySprite;
        this.runOnUpdateThread(new Runnable() {
            @Override
            public void run() {
				enemylayer.detachChild(Mosaikan.this.enemySpriteToRemove); // remove enemy
            }
        });
	}
	
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
	    MenuInflater inflater = getMenuInflater(); // create options when options button is pressed
	    inflater.inflate(R.menu.game_menu, menu); // from xml file
		return true;
	}
	
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
	    switch (item.getItemId()) {
		    case R.id.new_game: // new game = kill
		    	this.kill();
		        return true;
		    case R.id.license: // show short version of GPL
		    	showDialog(license_dialog_id);
		        return true;
		    case R.id.close: // close
			    Mosaikan.this.finish();
		        return true;
		    case R.id.menu: // start Menu.java activity
			    Mosaikan.this.startActivity(new Intent(Mosaikan.this, MenuScreen.class));
			    Mosaikan.this.finish(); // and close the game
		        return true;
		    default:
		        return super.onOptionsItemSelected(item);
	    }
	}

	private void reset() { // kill game activity and start a new one
	    Mosaikan.this.startActivity(new Intent(Mosaikan.this, Mosaikan.class));
	    Mosaikan.this.finish();
	}
	
	protected Dialog onCreateDialog(int id) {
		Dialog dialog = null;
		if (id==license_dialog_id ) {
			AlertDialog.Builder builder = new AlertDialog.Builder(this); // license pop-up
			builder.setMessage("Copyright (C) 2011 Ian Campbell\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License along with this program.  If not, see <http://www.gnu.org/licenses/>.")
			       .setCancelable(false)
			       .setPositiveButton("Close", new DialogInterface.OnClickListener() {
			           public void onClick(DialogInterface dialog, int id) {
			                dialog.cancel();
			           }
			       });
			AlertDialog alert = builder.create();
			dialog = alert;
		} else if (id==gameoverid ) { // game-over screen
			AlertDialog.Builder builder = new AlertDialog.Builder(this);
			if (noenemiesmode==false) {
			builder.setMessage("GAME OVER\n\nYou got "+Integer.toString(score)+" Points.")
			       .setCancelable(false)
			       .setPositiveButton("Close", new DialogInterface.OnClickListener() {
			           public void onClick(DialogInterface dialog, int id) {
			                dialog.cancel();
			                Mosaikan.this.reset();
			           }
			       });
			} else { // easy-mode game-over screen
				builder.setMessage("GAME OVER\n\nYou got "+Integer.toString(score)+" Points on easy mode.")
			       .setCancelable(false)
			       .setPositiveButton("Close", new DialogInterface.OnClickListener() {
			           public void onClick(DialogInterface dialog, int id) {
			                dialog.cancel();
			                Mosaikan.this.reset();
			           }
			       });
			}
			AlertDialog alert = builder.create();
			dialog = alert;
		}
		
	    return dialog;
	}

	@Override
	public void onModifierStarted(IModifier<IEntity> pModifier, IEntity pItem) {
		
	}
}